  var service = (function ($) {
    var access_token,
        api_base_url = '/api/v1';
    var METHODS = {
      GET: 'get',
      POST: 'post',
      DELETE: 'delete'
    },
    TYPES = {
      JSON: 'json',
      BLOB: 'blob'
    },
    apis = {
      search: {
        method: METHODS.GET,
        type: TYPES.JSON,
        api_name: 'search'
      },
      favor: {
        method: METHODS.POST,
        type: TYPES.JSON,
        api_name: 'favors'
      },
      unFavor: {
        method: METHODS.DELETE,
        type: TYPES.JSON,
        api_name: 'favors/{id}'
      },
      favors: {
        method: METHODS.GET,
        type: TYPES.JSON,
        api_name: 'favors'
      },
      novelDetail: {
        method: METHODS.GET,
        type: TYPES.JSON,
        api_name: 'novels/{id}',
      }
    },
    LIMIT = 15;

    var escape = encodeURIComponent;
    function genApiUrl(api, components, params) {
      var url = (api_base_url + '/' + api.api_name)
          .replace(/([^:])(\/{2,})/, function (match, p1) {
            return p1 + '/';
          });
      for (var key in components) {
        url = url.replace('{' + escape(key) + '}', escape(components[key]));
      }
      var query = "";
      for (var key in params) {
        if(query) query += "&";
        query += escape(key) + '=' + escape(params[key]);
      }
      if (query) url += '?' + query;
      return url;
    }

    function callService(api, components, params, body, onSuccess, onError){
      var options = {
        type: api.method,
        url: genApiUrl(api, components, params),
        headers: {Authorization: 'Bearer ' + access_token},
        dataType: api.type,
        success: onSuccess,
        error: onError
      }
      if (api.method == METHODS.POST) {
        options.contentType="application/json";
        options.data = JSON.stringify(body);
      }
      $.ajax(options) 
    }

    function getAccessToken () {
      var parts = document.cookie.split(';')
      for (var i = 0; i < parts.length; i++){
        var items = parts[i].split('=');
        if (items[0].trim() == 'access_token') {
          return items.length > 0 ? items[1].trim() : '';
        }
      }
      return '';
    }
    return {
      init: function(config) {
        if (!config) {
          access_token = getAccessToken();
        } else {
          access_token = config.token;
        }
      },
      search: function(query, onSuccess, onError, offset, limit) {
        if(!limit) limit = LIMIT;
        if(offset == undefined || offset < 0) offset = 0;
        callService(apis.search, null, {"q": query, limit: limit, offset: offset}, null, onSuccess, onError);
      },
      favor: function(novel_id, onSuccess, onError) {
        callService(apis.favor, null, null, {"novel_id": novel_id}, onSuccess, onError);
      },
      unFavor: function(favor_id, onSuccess, onError) {
        callService(apis.unFavor, {id: favor_id}, null, null, onSuccess, onError);
      },
      getFavorList: function(onSuccess, onError, offset, limit) {
        if(!limit) limit = LIMIT;
        if(offset == undefined || offset < 0) offset = 0;
        callService(apis.favors, null, {limit: limit, offset: offset}, null, onSuccess, onError);
      },
      getNovelDetail: function(novel_id, onSuccess, onError) {
        callService(apis.novelDetail, {id: novel_id}, null, null, onSuccess, onError);
      },
    }
  }(Zepto));
  service.init({})
